import json

import librosa
from fastdtw import fastdtw
from scipy.spatial.distance import euclidean
import os
from django.conf import settings

from .webrtc_vad import run_vad_on_wav, pcm_to_ndarray
from .models import AudioCache


class AudioRecognizer:
    def __init__(self, template_dir=settings.STATICFILES_DIRS[1], sample_rate=16000, n_mfcc=13):
        self.template_dir = template_dir
        self.sample_rate = sample_rate
        self.n_mfcc = n_mfcc
        self.templates = {}  # 用于存放模板特征的字典

    def query_audio_by_content(self, content):
        audio_obj = AudioCache.objects.filter(content=content).first()
        return audio_obj

    def query_audio_by_file_name(self, file_name):
        audio_obj = AudioCache.objects.filter(file_name=file_name).first()
        return audio_obj

    def query_audio_all(self):
        audio_objs = AudioCache.objects.all()
        return audio_objs

    def audio_mfcc_save(self, file_name, mfcc_features, content):
        # mfcc_features是列表格式，可直接json.dumps
        AudioCache(file_name=file_name, mfcc_features=json.dumps(mfcc_features), content=content).save()

    def load_audio(self, path):
        """加载音频文件并返回音频时序数据及采样率"""
        audio, sr = librosa.load(path, sr=self.sample_rate)
        return audio, sr

    def extract_mfcc_features(self, audio, sr):
        """提取MFCC特征，并转换为可JSON序列化的列表"""
        mfcc = librosa.feature.mfcc(y=audio, sr=sr, n_mfcc=self.n_mfcc)
        mfcc = mfcc.T  # (time_frames, n_mfcc)
        mfcc = mfcc.tolist()  # 将ndarray转换为列表
        return mfcc

    def build_templates(self, file_path, content):
        """
        对单个传入的音频文件进行特征提取并存储到数据库中。
        返回包含该文件特征的templates字典：{filename: [[mfcc_features], ...]}
        """
        fname = os.path.basename(file_path)  # 从路径中提取文件名
        audio_obj = self.query_audio_by_content(content)
        if audio_obj:
            # 数据库中已有该文件的MFCC特征
            self.templates[fname] = json.loads(audio_obj.mfcc_features)
        else:
            # 数据库中没有该文件记录，需要提取并保存
            if fname.endswith(".wav"):
                audio, sr = self.load_audio(file_path)
                mfcc_features = self.extract_mfcc_features(audio, sr)
                self.templates[fname] = [mfcc_features]
                # 保存到数据库
                self.audio_mfcc_save(fname, self.templates[fname], content)
            else:
                raise ValueError("文件不是wav格式")

        return self.templates

    def dtw_distance(self, mfcc1, mfcc2):
        """
        使用fastdtw计算两个MFCC序列的DTW距离
        mfcc1, mfcc2: shape (T, n_mfcc)的列表
        返回距离值
        """
        distance, _ = fastdtw(mfcc1, mfcc2, dist=euclidean)
        return distance

    def db_build_templates(self):
        audio_objs = self.query_audio_all()
        templates = {}
        for obj in audio_objs:
            templates[obj.content] = json.loads(obj.mfcc_features)
        self.templates = templates

    def recognize(self, audio_path):
        """
        给定待识别的音频文件路径和模板特征字典，输出识别结果
        """
        if not self.templates:
            raise ValueError("Templates not built. Please call process_audio_file() first.")
        audio, sr = self.load_audio(audio_path)

        test_mfcc = self.extract_mfcc_features(audio, sr)

        best_digit = None
        best_score = float('inf')

        # 遍历每个文件的模板
        for digit, mfcc_list in self.templates.items():
            # 将输入和该类别所有模板比较，取最小距离
            for tmpl_mfcc in mfcc_list:
                dist = self.dtw_distance(test_mfcc, tmpl_mfcc)
                if dist < best_score:
                    best_score = dist
                    best_digit = digit

        return best_digit, best_score

    def recognize_with_vad(self, audio_path):
        # 1. 通过VAD获取有效语音片段
        segments = run_vad_on_wav(audio_path, aggressiveness=3)

        if not segments:
            return None, "No voice detected."

        all_candidates = []
        # 2. 对所有语音片段做识别
        for seg in segments:
            audio_nd = pcm_to_ndarray(seg, sample_rate=16000)
            mfcc_features = librosa.feature.mfcc(y=audio_nd, sr=16000, n_mfcc=13)
            mfcc_features = mfcc_features.T
            # 3. DTW匹配：与数据库中的模板进行比对
            best_digit = None
            best_score = float('inf')
            for digit, tmpl_list in self.templates.items():
                for tmpl_mfcc in tmpl_list:
                    dist = self.dtw_distance(mfcc_features.tolist(), tmpl_mfcc)
                    if dist < best_score:
                        best_score = dist
                        best_digit = digit
            all_candidates.append((best_digit, best_score))

        # 4. 看看哪个识别结果最好
        if all_candidates:
            best_candidate = min(all_candidates, key=lambda x: x[1])
            return best_candidate  # (digit, distance)
        else:
            return None, "No valid speech segments recognized."

    def delete_audio_file(self, file_path):
        os.remove(file_path)


class CardCounter:
    def __init__(self, deck=None):
        if deck:
            self.deck = deck
        else:
            self.initial_deck = {
                'A': 3,  # A
                '2': 1,  # 2（麻子）
                '3': 4,
                '4': 4,
                '5': 4,
                '6': 4,
                '7': 4,
                '8': 4,
                '9': 4,
                '10': 4,
                'J': 4,
                'Q': 4,
                'K': 4
            }
            self.deck = self.initial_deck.copy()

    def reset_deck(self):
        self.deck = self.initial_deck.copy()
        return self.deck

    def show_deck(self):
        # 按牌的顺序排序
        sorted_deck = dict(sorted(self.deck.items(), key=lambda x: self.rank_value(x[0])))
        return sorted_deck

    def rank_value(self, card):
        """用于排序牌的辅助函数"""
        order = {'A': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8,
                 '9': 9, '10': 10, 'J': 11, 'Q': 12, 'K': 13}
        return order.get(card.upper(), 0)

    def play_cards(self, cards):
        result = []
        for card in cards:
            card = card.upper().strip()
            if card not in self.deck:
                result.append({"card": card, "status": "invalid"})
                continue
            if self.deck[card] > 0:
                self.deck[card] -= 1
                result.append({"card": card, "status": "played", "remaining": self.deck[card]})
            else:
                result.append({"card": card, "status": "none_left"})
        return result
